﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// Simple Service Administration Tool for WinNT based systems.
// Copyright © 2010 - Silvan Gehrig
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
// 
// Author:
//  Silvan Gehrig
//
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Threading;
using SSATool.Common.Util.WeakEvents;
using SSATool.DL.Common.Dto;
using SSATool.Common.Util.ExtensionMethods;
using SSATool.Common.Util.Threading;

#endregion

namespace SSATool.DL.ServiceModel
{
    /// <summary>
    /// Contains the facilities to retrieve and store process service
    /// information and status.
    /// </summary>
    public class ServiceProcessManager
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private readonly ReaderWriterLock _lock = new ReaderWriterLock();
        private readonly ServiceProcessWatcher _watcher;

        private Dictionary<string, ServiceController> _services = new Dictionary<string, ServiceController>();
        private EventHandler<ServiceEventArgs> _onServiceStateChange;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Specifies the event which is raised if a service state changes.
        /// </summary>
        /// <remarks>
        /// This event will be fired in a separate thread context.
        /// </remarks>
        public event EventHandler<ServiceEventArgs> ServiceStateChange
        {
            add { using (_lock.AcquireWriterLock()) { _onServiceStateChange += value; } }
            remove { using (_lock.AcquireWriterLock()) { _onServiceStateChange -= value; } }
        }

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceProcessManager"/> class.
        /// </summary>
        public ServiceProcessManager()
        {
            Merge();

            _watcher = new ServiceProcessWatcher(OnListUpdate);
            _watcher.Start();
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets a list of all service controllers.
        /// </summary>
        /// <returns>Returns the service list.</returns>
        public IList<ServiceController> GetServiceList()
        {
            using (_lock.AcquireReaderLock())
            {
                return new List<ServiceController>(_services.Values);
            }
        }

        /// <summary>
        /// Gets the service with the given unique name.
        /// </summary>
        /// <param name="uniqueServiceName">Name of the service to retrieve.</param>
        /// <returns>Returns the retrieved service.</returns>
        public ServiceController GetService(string uniqueServiceName)
        {
            using (_lock.AcquireReaderLock())
            {
                if (_services.ContainsKey(uniqueServiceName))
                    return _services[uniqueServiceName];
                return null;
            }
        }

        /// <summary>
        /// Sends the changes on a service back to the host.
        /// </summary>
        /// <param name="service">Specifies the service to update.</param>
        public void SetService(ServiceController service)
        {
            using (_lock.AcquireWriterLock())
            {
                new ServiceModelAdapter().SetServiceState(service);
            }
            Update();
        }

        private void Update()
        {
            IList<ServiceControllerChange> changes;

            using (_lock.AcquireWriterLock())
            {
                changes = Merge();
            }
            
            if (changes.Count > 0)
                new AsyncProcess().Run(() => OnServiceStateChange(new ServiceEventArgs { Changes = changes }));
        }

        private IList<ServiceControllerChange> Merge()
        {
            return Merge(new ServiceModelAdapter().GetServices());
        }

        private IList<ServiceControllerChange> Merge(Dictionary<string, ServiceController> newServices)
        {
            IList<ServiceControllerChange> changes = new List<ServiceControllerChange>();

            foreach (KeyValuePair<string, ServiceController> currentService in _services) // update existing 
            {
                if (newServices.ContainsKey(currentService.Key))
                {
                    ServiceController newService = newServices[currentService.Key];

                    if (!ServiceStatesAreEqual(currentService.Value, newService))
                    {
                        changes.Add(new ServiceControllerChange { Reason = ServiceControllerChangeReason.Updated, Service = newService });
                    }
                }
                else
                {
                    changes.Add(new ServiceControllerChange { Reason = ServiceControllerChangeReason.Deleted, Service = currentService.Value });
                }
            }

            foreach (KeyValuePair<string, ServiceController> newService in newServices)
            {
                if (!_services.ContainsKey(newService.Key))
                {
                    changes.Add(new ServiceControllerChange { Reason = ServiceControllerChangeReason.Added, Service = newService.Value });
                }
            }
            
            _services = newServices;
            return changes;
        }

        private bool ServiceStatesAreEqual(ServiceController left, ServiceController right)
        {
            if (left == right)
                return true;

            if (left == null)
                return false;

            return (left.Status == right.Status);
        }


        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        /// <summary>
        /// Fires the ServiceStateChange event.
        /// </summary>
        /// <param name="eventArgs">Eventargs to pass to the event receiver.</param>
        protected virtual void OnServiceStateChange(ServiceEventArgs eventArgs)
        {
            using (_lock.AcquireReaderLock())
            {
                if (_onServiceStateChange != null)
                    _onServiceStateChange(this, eventArgs);
            }
        }

        private void OnListUpdate()
        {
            Update();
        }

        #endregion
    }
}
